
#include <cegfx/cemacros.h>
#include <cegfx/ceeffect.h>
#include <cegfx/ceinstancedmesh.h>
#include <cegfx/cevirtualclock.h>

ceInstancedMesh::ceInstancedMesh ()
	: vertexBuffer (0)
	, numVertices(0)
	, indexBuffer (0)
	, numTriangles (0)
	, vertexDeclaration(0)
	, stride (0)
	, instanceRadius (0.0f)
{
	ClearInstances();
}

ceInstancedMesh::~ceInstancedMesh ()
{
	if (vertexBuffer)
	{
		vertexBuffer->Release ();
	}
	if (indexBuffer)
	{
		indexBuffer->Release ();
	}
	if (vertexDeclaration)
	{
		vertexDeclaration->Release ();
	}
}

IQF_IMPLEMENTATION_BEGIN (ceInstancedMesh);
IQF_IMPLEMENTATION_INTERFACE(iGeometry);
IQF_IMPLEMENTATION_END ();


void ceInstancedMesh::SetVertices (LPDIRECT3DVERTEXBUFFER9 vertexBuffer, unsigned numVertices)
{
	if (vertexBuffer)
	{
		vertexBuffer->AddRef ();
	}
	if (this->vertexBuffer)
	{
		this->vertexBuffer->Release ();
	}
	this->vertexBuffer = vertexBuffer;
	this->numVertices = numVertices;
}

LPDIRECT3DVERTEXBUFFER9 ceInstancedMesh::GetVertexBuffer ()
{
	return vertexBuffer;
}

const LPDIRECT3DVERTEXBUFFER9 ceInstancedMesh::GetVertexBuffer () const
{
	return vertexBuffer;
}

unsigned ceInstancedMesh::GetNumVertices () const
{
	return numVertices;
}

void ceInstancedMesh::SetVertexDeclaration (LPDIRECT3DVERTEXDECLARATION9 declaration, unsigned stride)
{
	if (declaration)
	{
		declaration->AddRef ();
	}
	if (vertexDeclaration)
	{
		vertexDeclaration->Release ();
	}

	vertexDeclaration = declaration;
	this->stride = stride;
}

LPDIRECT3DVERTEXDECLARATION9 ceInstancedMesh::GetVertexDeclaration ()
{
	return vertexDeclaration;
}

const LPDIRECT3DVERTEXDECLARATION9 ceInstancedMesh::GetVertexDeclaration () const
{
	return vertexDeclaration;
}

unsigned ceInstancedMesh::GetStride () const
{
	return stride;
}

void ceInstancedMesh::SetIndices (LPDIRECT3DINDEXBUFFER9 indexBuffer, unsigned numTriangles)
{
	if (indexBuffer)
	{
		indexBuffer->AddRef ();
	}
	if (this->indexBuffer)
	{
		this->indexBuffer->Release ();
	}
	this->indexBuffer = indexBuffer;
	this->numTriangles = numTriangles;
}

LPDIRECT3DINDEXBUFFER9 ceInstancedMesh::GetIndexBuffer ()
{
	return indexBuffer;
}

const LPDIRECT3DINDEXBUFFER9 ceInstancedMesh::GetIndexBuffer () const
{
	return indexBuffer;
}

unsigned ceInstancedMesh::GetNumberOfTriangles () const
{
	return numTriangles;
}


void ceInstancedMesh::SetInstanceRadius (float radius)
{
	instanceRadius = radius;
}

void ceInstancedMesh::ClearInstances ()
{
	part.bbox.Clear();
	part.parts.clear();
	part.positions.clear();
}

void ceInstancedMesh::AddInstance (const D3DXVECTOR3& pos)
{
	part.positions.push_back (D3DXVECTOR4(pos.x, pos.y, pos.z, 0.0f));
}

void ceInstancedMesh::UpdateBoundingBox ()
{
	UpdateBoundingBox (part);
}

void ceInstancedMesh::UpdateBoundingBox (Part& part)
{
	part.bbox.Clear();

	FOR_VECTOR(unsigned, i, j, part.positions)
	{
		D3DXVECTOR4& v = part.positions[i];
		part.bbox.AddVector3 (v.x, v.y, v.z);
	}

	FOR_VECTOR(unsigned, i, j, part.parts)
	{
		Part& p = part.parts[i];
		UpdateBoundingBox (p);
		part.bbox.AddBoundingBox (p.bbox);
	}

	part.bbox.AddBorder (instanceRadius, instanceRadius, instanceRadius);
	part.bbox.Update();
}

bool ceInstancedMesh::Pack (LPDIRECT3DDEVICE9 device)
{
	return Optimize (device, part);


	return true;
}

bool ceInstancedMesh::Optimize (LPDIRECT3DDEVICE9 device, Part& part)
{
	struct Vertex
	{
		float x, y, z, w;
	};

	// create the data for the instancing
	void* data =0;
	if (FAILED(device->CreateVertexBuffer (sizeof (Vertex) * part.positions.size(), 0, 0, D3DPOOL_DEFAULT, &part.instanceBuffer, 0))) return false;
	if (FAILED(part.instanceBuffer->Lock (0, sizeof(Vertex) * part.positions.size (), &data, 0))) return false;

	Vertex* vptr = static_cast<Vertex*>(data);
	FOR_VECTOR (unsigned, i, j, part.positions)
	{
		vptr->x = part.positions[i].x;
		vptr->y = part.positions[i].y;
		vptr->z = part.positions[i].z;
		vptr->w = 0.0f;
		vptr++;
	}
	part.instanceBuffer->Unlock();

	return true;
}


void ceInstancedMesh::Render (const ceRenderEnv& env)
{
	LPDIRECT3DDEVICE9 device = env.device;
	ceEffect* effect = ceEffect::GetCurrentEffect();
	if (!effect)
	{
		return;
	}

	device->SetVertexDeclaration (vertexDeclaration);
	device->SetStreamSource (0, vertexBuffer, 0, stride);
	device->SetIndices (indexBuffer);

	device->SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE);
	RenderPart (part, device, effect, 0, env.clipper);

	device->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, numVertices, 0, numTriangles);
	device->SetRenderState (D3DRS_CULLMODE, D3DCULL_CCW);
}

void ceInstancedMesh::RenderPart (Part& part, LPDIRECT3DDEVICE9 device, ceEffect* effect, D3DXHANDLE handle, const ceClipper* clipper)
{
	// setup the instancing
	device->SetStreamSourceFreq (0, D3DSTREAMSOURCE_INDEXEDDATA | part.positions.size ());

	device->SetStreamSource (1, part.instanceBuffer, 0, sizeof (float) * 4);
	device->SetStreamSourceFreq( 1, D3DSTREAMSOURCE_INSTANCEDATA | 1ul );

	device->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, numVertices, 0, numTriangles);

	device->SetStreamSource (1, 0, 0, 0);
	device->SetStreamSourceFreq (0, 1);
	device->SetStreamSourceFreq (1, 1);

	FOR_VECTOR(unsigned, i, j, part.parts)
	{
		RenderPart (part.parts[i], device, effect, handle, clipper);
	}
}


void ceInstancedMesh::Update (const ceUpdateEnv& env)
{
}

const ceBoundingBox& ceInstancedMesh::GetBoundingBox () const
{
	return part.bbox;
}

